require 'attribute'

module ArtOfRails
  module Rdf
    module ActiveRecord    
      class RdfSerializer #:nodoc:
        attr_accessor :global_options
        
        def initialize(rdf_source, options = {})
          @rdf_source, @global_options = rdf_source, options.dup
        end
        
        def options_for_record(record)
          record_options = record.class.rdf_export_configuration
          # record_options = record_options.update(global_options) if global_options.is_a?(Hash)          
          record_options
        end

        def builder
          @builder ||= begin
            global_options[:indent] ||= 2
            builder = global_options[:builder] ||= Builder::XmlMarkup.new(:indent => global_options[:indent])

            unless global_options[:skip_instruct]
              builder.instruct!
              global_options[:skip_instruct] = true
            end

            builder
          end
        end

        def dasherize?
          !global_options.has_key?(:dasherize) || global_options[:dasherize]
        end

        # To replicate the behavior in ActiveRecord#attributes,
        # :except takes precedence over :only.  If :only is not set
        # for a N level model but is set for the N+1 level models,
        # then because :except is set to a default value, the second
        # level model can have both :except and :only set.  So if
        # :only is set, always delete :except.
        def serializable_attributes(record, options)          
          attribute_names = record.attribute_names

          if options[:only]
            options.delete(:except)
            attribute_names = attribute_names & Array(options[:only]).collect { |n| n.to_s }
          else
            options[:except] = Array(options[:except]) | Array(record.class.inheritance_column)
            attribute_names = attribute_names - options[:except].collect { |n| n.to_s }
          end

          attribute_names.collect { |name| Attribute.new(name, record) }
        end

        def serializable_method_attributes(record, options)
          method_attributes = []          
          if (options[:methods])
            options[:methods].each_pair do | method,property |
              method_attributes << MethodAttribute.new(method.to_s, record, property) if record.respond_to?(method.to_s)
            end 
          end
          return method_attributes
        end

        def add_attributes_for(record, options)
          (serializable_attributes(record, options) + serializable_method_attributes(record, options)).each do |attribute|
            add_attribute(attribute, options)
          end
        end

        def add_includes_for(record, options)
          if include_associations = options.delete(:include)
            root_only_or_except = { :except => options[:except],
                                    :only => options[:only] }

            assoc_options_given = include_associations.is_a? Hash
            for association in (assoc_options_given ? include_associations.keys : include_associations)
              assoc_options = assoc_options_given ? include_associations[association] : { :map => association }
              if assoc_options.is_a? String
                assoc_options = { :map => assoc_options }
              end
              
              property = assoc_options[:map]

              case record.class.reflect_on_association(association).macro
              when :has_many, :has_and_belongs_to_many
                records = record.send(association).to_a
                unless records.empty?
                  records.each do |object| 
                    attribute = IndividualAttribute.new(association, record, property, object, assoc_options)
                    add_attribute attribute, options
                  end
                end
              when :has_one, :belongs_to
                if object = record.send(association)
                  attribute = IndividualAttribute.new(association, record, property, object, assoc_options)
                  add_attribute attribute, options                   
                end
              end
            end

            options[:include] = include_associations
          end
        end

        def add_procs
          if procs = global_options.delete(:procs)
            [ *procs ].each do |proc|
              proc.call(options)
            end
          end
        end

        def add_record(record, additional_options = {})
          options = options_for_record(record).update(additional_options)
          args = []
          if options[:class_type]
            args << options[:class_type]
          else
            args << 'rdf:Description'
          end
          
          args << {:"rdf:about" => record.resource_uri}


          builder.tag!(*args) do
            add_attributes_for record, options
            add_includes_for record, options
            yield builder if block_given?        
          end
          
        end
        
        def add_attribute(attribute, options)
          if attribute.is_a? IndividualAttribute
            if (attribute.style == :inline)
              builder.tag!(attribute.property) { add_record attribute.object, attribute.options }
            elsif (attribute.style == :reference)
              builder.tag!(attribute.property, {:"rdf:resource", attribute.value})
            end
          else
            builder.tag!(
              # dasherize? ? attribute.name.dasherize : attribute.name, 
              attribute.property, 
              attribute.value.to_s, 
              attribute.decorations(!options[:skip_types])
            )
          end
        end

        def serialize
          args = ['rdf:RDF']
          # Set up the namespaces. RDF is a required one.
          ArtOfRails::Rdf::Namespaces.hash.each_pair { |ns, val|
            args << {:"xmlns:#{ns}", val} unless args.include?({:"xmlns:#{ns}", val})           
          }                            

          builder.tag!(*args) do
            if (@rdf_source.is_a? Array)
              @rdf_source.each do |record|  
                add_record record
              end
            else
              add_record @rdf_source
            end
            add_procs
            yield builder if block_given?
          end
        end                
      
        alias_method :to_s, :serialize

      end #end class
    end #end module ActiveRecord
  end #end module Rdf
end #end module ArtOfRails